package br.mikhas.util.jdbc.tx;

import java.sql.Connection;
import java.sql.SQLException;

import javax.sql.DataSource;

import br.mikhas.util.Assert;
import br.mikhas.util.jdbc.JdbcTemplate;
import br.mikhas.util.jdbc.JdbcUtils;

/**
 * Encapsulates the execution and management of a DataSource transaction.
 * <p>
 * The scope is responsible by transaction start, commit and rollback.
 * 
 * @author Mikhail Domanoski
 * 
 */
public class TransactionScope {

	/**
	 * The data source
	 */
	private final DataSource dataSource;

	/**
	 * Creates a new {@link TransactionScope} instance and defies the managed
	 * datasource.
	 * 
	 * @param dataSource
	 *            The managed datasource
	 */
	public TransactionScope(DataSource dataSource) {
		Assert.notNull(dataSource);
		this.dataSource = dataSource;
	}

	/**
	 * Creates a new {@link TransactionScope} instance providing a JdbcTemplate
	 * which contains the datasource which will be maneged
	 * 
	 * @param template
	 *            The managed template
	 */
	public TransactionScope(JdbcTemplate template) {
		Assert.notNull(template);
		this.dataSource = template.getDataSource();
		Assert.notNull(this.dataSource);
	}

	/**
	 * Causes the transaction begin and the execution of it's statements
	 * 
	 * @param transaction
	 *            The transaction to be executed
	 */
	public void execute(Transaction transaction) {
		Assert.notNull(transaction);
		beforeTransaction();

		try {
			transaction.inTransaction();
		} catch (Exception e) {
			rollback();
			throw new TransactionInterruptedException(
					"An error occurred during transaction: " + e.getMessage(),
					e);
		}

		afterTransaction();
	}

	/**
	 * Execute transaction preparation actions before it begin
	 */
	private void beforeTransaction() {
		TransactionUtils.beginTransaction();

		Connection connection = TransactionUtils.getConnection(this.dataSource);

		try {
			if (connection.getAutoCommit())
				connection.setAutoCommit(false);
		} catch (SQLException e) {
			throw new TransactionException("Could not begin transaction.", e);
		}
	}

	/**
	 * Execute transaction commit and ends the transaction management
	 */
	private void afterTransaction() {
		Connection connection = TransactionUtils.getConnection(this.dataSource);

		try {
			connection.commit();

			TransactionUtils.releaseConnection(this.dataSource, connection);
		} catch (SQLException e) {
			throw new TransactionException(
					"Could not commit current transaction.", e);
		} finally {
			TransactionUtils.endTransaction();
			JdbcUtils.close(connection);
		}
	}

	/**
	 * Rollback the current transaction
	 */
	private void rollback() {
		Connection connection = TransactionUtils.getConnection(this.dataSource);
		try {
			connection.rollback();
			TransactionUtils.releaseConnection(this.dataSource, connection);
		} catch (SQLException e) {
			throw new TransactionException(
					"Cold not rollback current transaction", e);
		} finally {
			TransactionUtils.endTransaction();
			JdbcUtils.close(connection);
		}
	}
}
